/*
 * Copyright (c) 2010, Brookhaven National Laboratory
 * All rights reserved.
 * The software in this package is published under the terms
 * of the BSD license a copy of which has been included with
 * this distribution in the license.txt file.
 */

package org.oxtypes.protobuf;

import java.util.List;
import java.util.LinkedList;
import java.util.Map;
import java.util.TreeMap;

import org.omg.dds.type.dynamic.DynamicType;
import org.omg.dds.type.dynamic.modifiable.ModifiableTypeDescriptor;
import org.omg.dds.type.dynamic.DynamicTypeMember;
import org.omg.dds.type.dynamic.modifiable.ModifiableMemberDescriptor;
import org.omg.dds.type.dynamic.AnnotationDescriptor;

import com.google.protobuf.Descriptors.Descriptor;
import com.google.protobuf.Descriptors.FieldDescriptor;
import org.omg.dds.type.TypeKind;

import org.oxtypes.impl.DynamicTypeImpl;
import org.oxtypes.impl.modifiable.ModifiableTypeDescriptorImpl;
import org.oxtypes.impl.DynamicTypeMemberImpl;
import org.oxtypes.impl.modifiable.ModifiableMemberDescriptorImpl;
import org.oxtypes.impl.DynamicTypeManagerImpl;

/**
 *
 */
public class DynamicTypeRegistry {

   /** Returns singleton */
   static public DynamicTypeRegistry getInstance(){
        if(singleton == null){
            singleton = new DynamicTypeRegistry();
        }
        return singleton;
   }

   public DynamicType registryDescriptor(Descriptor d){
       if (descriptors.containsKey(d.getFullName())){
         throw new RuntimeException(d.getName() + ": already registred");
       }

       DynamicType dt = buildDynamicType(d);

       if(dt == null) {
           throw new RuntimeException(d.getName() + ": failed to build DynamicType");
       }

       types.put(dt.getName(), dt);
       descriptors.put(d.getFullName(), d);

       dtm.loadType(dt);

       return dt;
   }

   public Descriptor getDescriptor(DynamicType type){
        return descriptors.get(type.getName());
   }

   private DynamicTypeRegistry(){
        dtm = DynamicTypeManagerImpl.getInstance();
        types = new TreeMap<String, DynamicType>();
        descriptors = new TreeMap<String, Descriptor>();
   }
   
   private DynamicType buildDynamicType(Descriptor d){

       // Allocate type's descriptor, members, and annotations

       ModifiableTypeDescriptor mtd =
               new ModifiableTypeDescriptorImpl();
       List<DynamicTypeMember> members = 
               new LinkedList<DynamicTypeMember>();
       List<AnnotationDescriptor> ads =
               new LinkedList<AnnotationDescriptor>();

       // Set type's descriptor

       setTypeDescriptor(d, mtd, ads);

       mtd.finishModification();

       // Set type's members

       List<FieldDescriptor> fields = d.getFields();

       for(FieldDescriptor fd : fields){
           DynamicTypeMember dtm = buildDynamicTypeMember(fd);
           members.add(dtm);
       }

       // d.getContainingType();

       // List<FieldDescriptor> extensions = d.getExtensions();

       // List<EnumDescriptor> enumTypes  = d.getEnumTypes();

       // List<Descriptor> nestedTypes = d.getNestedTypes();

       DynamicTypeImpl dt = new DynamicTypeImpl(mtd, members, ads);

       return dt;
   }

   private void setTypeDescriptor(Descriptor d,
           ModifiableTypeDescriptor mtd, List<AnnotationDescriptor> ads){
       mtd.setKind(TypeKind.STRUCTURE_TYPE);
       mtd.setName(d.getFullName());
   }


   private DynamicTypeMember buildDynamicTypeMember(FieldDescriptor fd){

       // Allocate member's descriptor and annotations

       ModifiableMemberDescriptor mmd =
               new ModifiableMemberDescriptorImpl();
       List<AnnotationDescriptor> ads =
               new LinkedList<AnnotationDescriptor>();

       // Set member's descriptor

       mmd.setName(fd.getName());
       mmd.setId(fd.getNumber());
       mmd.setIndex(fd.getIndex());

       // Set member's type

       if(fd.isRepeated()) setSequenceType(fd, mmd, ads);
       else setMemberType(fd, mmd, ads);

       mmd.finishModification();

       //

       DynamicTypeMember typeMember = new DynamicTypeMemberImpl(mmd, ads);

       return typeMember;
   }
   
   private void setMemberType(FieldDescriptor fd,
           ModifiableMemberDescriptor mmd, List<AnnotationDescriptor> ads){
       switch (fd.getLiteType()) {
                case DOUBLE  : 
                    mmd.setType(dtm.getPrimitiveType(TypeKind.FLOAT_64_TYPE));
                    break;
                case FLOAT   : 
                    mmd.setType(dtm.getPrimitiveType(TypeKind.FLOAT_32_TYPE));
                    break;
                case INT64   :
                case SINT64  :
                case UINT64  :
                case FIXED64 :
                case SFIXED64: 
                    setInt64Type(fd, mmd, ads);
                    break;
                case INT32   :
                case SINT32  :
                case UINT32  :
                case FIXED32 :
                case SFIXED32:
                    setInt32Type(fd, mmd, ads);
                    break;
                case BOOL    : 
                    mmd.setType(dtm.getPrimitiveType(TypeKind.BOOLEAN_TYPE));
                    break;
                case STRING  : break;
                case BYTES   : break;
                case ENUM    : break;
                case GROUP   : break;
                case MESSAGE : break;
                default: break;
        }
   }

   private void setSequenceType(FieldDescriptor fd,
           ModifiableMemberDescriptor mmd, List<AnnotationDescriptor> ads){

       DynamicType elementType = null;

       switch (fd.getLiteType()) {
                case DOUBLE  :
                    elementType = dtm.getPrimitiveType(TypeKind.FLOAT_64_TYPE);
                    break;
                case FLOAT   :
                    elementType = dtm.getPrimitiveType(TypeKind.FLOAT_32_TYPE);
                    break;
                case INT64   :
                case SINT64  :
                case UINT64  :
                case FIXED64 :
                case SFIXED64:
                    setInt64Type(fd, mmd, ads);
                    break;
                case INT32   :
                case SINT32  :
                case UINT32  :
                case FIXED32 :
                case SFIXED32:
                    setInt32Type(fd, mmd, ads);
                    break;
                case BOOL    :
                    elementType = dtm.getPrimitiveType(TypeKind.BOOLEAN_TYPE);
                    break;
                case STRING  : break;
                case BYTES   : break;
                case ENUM    : break;
                case GROUP   : break;
                case MESSAGE : break;
                default: break;
        }

       DynamicType type = dtm.createSequenceType(elementType, 0);

       mmd.setType(type);
   }

   private void setInt64Type(FieldDescriptor fd,
         ModifiableMemberDescriptor mmd, List<AnnotationDescriptor> ads){

       mmd.setType(dtm.getPrimitiveType(TypeKind.INT_64_TYPE));

   }

   private void setInt32Type(FieldDescriptor fd,
         ModifiableMemberDescriptor mmd, List<AnnotationDescriptor> ads){

       mmd.setType(dtm.getPrimitiveType(TypeKind.INT_32_TYPE));

   }

   // members

   private static DynamicTypeRegistry singleton;

   private DynamicTypeManagerImpl dtm;

   private Map<String, DynamicType> types;
   private Map<String, Descriptor> descriptors;

}
